#include <iostream>

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_bind.hpp>
#include <assert.h>

struct date {
	unsigned short year;
	unsigned short month;
	unsigned short day;
};

struct datetime {
	enum zone_offsets_t {
		OFFSET_NOT_SET,
		OFFSET_Z,
		OFFSET_UTC_PLUS,
		OFFSET_UTC_MINUS
	};

private:
	unsigned short year_;
	unsigned short month_;
	unsigned short day_;
	unsigned short hours_;
	unsigned short minutes_;
	unsigned short seconds_;
	zone_offsets_t zone_offset_type_;
	unsigned int zone_offset_in_min_;

	static void dt_assert(bool v, const char* msg) {
		if (!v) {
			throw std::logic_error("Assertion failed: " + std::string(msg));
		}
	}

public:
	datetime()
		: year_(0), month_(0), day_(0)
		, hours_(0), minutes_(0), seconds_(0)
		, zone_offset_type_(OFFSET_NOT_SET), zone_offset_in_min_(0)
	{}

	void set_year(unsigned short y)
	{
		year_ = y;
	}

	void set_month(unsigned short m)
	{
		month_ = m;
	}

	void set_day(unsigned short d)
	{
		day_ = d;
	}

	void set_hours(unsigned short h)
	{
		hours_ = h;
	}

	void set_minutes(unsigned short m)
	{
		minutes_ = m;
	}

	void set_seconds(unsigned short s)
	{
		seconds_ = s;
	}

	void set_zone_offset_type(zone_offsets_t zot)
	{
		zone_offset_type_ = zot;
	}

	void set_zone_offset_in_min(unsigned int zoim)
	{
		zone_offset_in_min_ = zoim;
	}

	void print()
	{
		std::cout << "year_:" << year_ << "    "
			<< "month_:" << month_ << "    "
			<< "day_:" << day_ << "    "
			<< "hours_:" << hours_ << "    "
			<< "minutes_:" << minutes_ << "    "
			<< "seconds_:" << seconds_ << "    "
			<< "zone_offset_type_:" << zone_offset_type_ << "    "
			<< "zone_offset_in_min_:" << zone_offset_in_min_ << "    "
			<< std::endl;
	}
};

void set_zone_offset(datetime dt, char sign, unsigned short hours, unsigned short minutes) {
	dt.set_zone_offset_type(sign == '+' ? datetime::OFFSET_NOT_SET : datetime::OFFSET_UTC_MINUS);
	dt.set_zone_offset_in_min(hours * 60 + minutes);
}

date parse_date_time1(const std::string& s) {
	using boost::spirit::qi::_1;
	using boost::spirit::qi::ushort_;
	using boost::spirit::qi::char_;
	using boost::phoenix::ref;

	date res;
	const char* first = s.data();
	const char* const end = first + s.size();
	bool success = boost::spirit::qi::parse(first, end,
		ushort_[ ref(res.year) = _1 ] >> char('-')
		>> ushort_[ ref(res.month) = _1 ] >> char('-')
		>> ushort_[ ref(res.day) = _1 ]
	);
	if (!success || first != end) {
		throw std::logic_error("Parsing failed");
	}
	return res;
}

date parse_date_time2(const std::string& s) {
	using boost::spirit::qi::_1;
	using boost::spirit::qi::uint_parser;
	using boost::spirit::qi::char_;
	using boost::phoenix::ref;

	uint_parser<unsigned short, 10, 2, 2> u2_;
	uint_parser<unsigned short, 10, 4, 4> u4_;

	date res;
	const char* first = s.data();
	const char* const end = first + s.size();
	bool success = boost::spirit::qi::parse(first, end,
		u4_[ref(res.year) = _1] >> char_('-')
		>> u2_[ref(res.month) = _1] >> char_('-')
		>> u2_[ref(res.day) = _1]
	);
	if (!success || first != end) {
		throw std::logic_error("Parsing failed");
	}
	return res;
}

datetime parse_datetime(const std::string& s) {
	using boost::spirit::qi::_1;
	using boost::spirit::qi::_2;
	using boost::spirit::qi::_3;
	using boost::spirit::qi::uint_parser;
	using boost::spirit::qi::char_;
	using boost::phoenix::bind;
	using boost::phoenix::ref;

	datetime ret;

	uint_parser<unsigned short, 10, 2, 2> u2_;
	uint_parser<unsigned short, 10, 4, 4> u4_;

	boost::spirit::qi::rule<const char*, void()> timezone_parser
		= -(
			char_('Z')[
				bind(&datetime::set_zone_offset_type, &ret, datetime::OFFSET_Z)
			] |
			((char_('+') | char_('-')) >> u2_ >> ":" >> u2_)[
				bind(&set_zone_offset, ref(ret), _1, _2, _3)
			]
			);

	boost::spirit::qi::rule<const char*, void()> date_parser =
		u4_[bind(&datetime::set_year, &ret, _1)] >> char_('-')
		>> u2_[bind(&datetime::set_month, &ret, _1)] >> char_('-')
		>> u2_[bind(&datetime::set_day, &ret, _1)];

	boost::spirit::qi::rule<const char*, void()> time_parser =
		u2_[bind(&datetime::set_hours, &ret, _1)] >> char_(':')
		>> u2_[bind(&datetime::set_minutes, &ret, _1)] >> char_(':')
		>> u2_[bind(&datetime::set_seconds, &ret, _1)];

	const char* first = s.data();
	const char* const end = first + s.size();
	bool success = boost::spirit::qi::parse(first, end,
		((date_parser >> char_('T') >> time_parser) | date_parser | time_parser)
		>> timezone_parser
	);
	if (!success || first != end) {
		throw std::logic_error("Parsing of '" + s + "' failed");
	}
	return ret;
}

int main() {
	date d = parse_date_time1("2012-12-31");
	std::cout << "==== 1 ====" << "    " << std::endl
		<< "d.year:" << d.year << "    "
		<< "d.month:" << d.month << "    "
		<< "d.day:" << d.day << "    "
		<< std::endl;

	d = parse_date_time2("2012-12-31");
	std::cout << "==== 2 ====" << "    " << std::endl
		<< "d.year:" << d.year << "    "
		<< "d.month:" << d.month << "    "
		<< "d.day:" << d.day << "    "
		<< std::endl;

	datetime d1 = parse_datetime("2012-10-20T10:00:00Z");
	std::cout << "==== 3 ====" << "    " << std::endl;
	d1.print();
	d1 = parse_datetime("2012-10-20T10:00:00");
	std::cout << "==== 4 ====" << "    " << std::endl;
	d1.print();
	d1 = parse_datetime("2012-10-20T10:00:00+09:15");
	std::cout << "==== 5 ====" << "    " << std::endl;
	d1.print();
	d1 = parse_datetime("2012-10-20-09:15");
	std::cout << "==== 6 ====" << "    " << std::endl;
	d1.print();
	d1 = parse_datetime("10:00:09+09:15");
	std::cout << "==== 7 ====" << "    " << std::endl;
	d1.print();
	return 0;
}
